﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Collections.Specialized;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using System.Collections;

namespace ReactiveCode.Statements
{
    using Observables;
    using Linq;
    using Util;

    class ForEach<T> : ReactiveStatement
    {
        class ForEachItemStatement : ProxyStatement
        {
            public IObservable<T> Item { get; private set; }

            public ForEachItemStatement(IObservable<T> item, Func<IObservable<T>, ReactiveStatement> stmtFactory)
                : base(null)
            {
                Contract.Requires(item != null && stmtFactory != null);
                Item = item;
                Real = stmtFactory(item);
            }
        }
        Func<IObservable<T>, ReactiveStatement> _bodySelector;
        Block _block;
        IDisposable _subscription;

        Collection<ReactiveStatement> Body
        {
            get { return _block.Body; }
        }

        ReactiveStatement CreateStatement(IObservable<T> item)
        {
            try
            {
                return _bodySelector(item);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("An exception was thrown while executing 'body' delegate", ex);
            }
        }
        public ForEach(ObservableFactory<IObservableEnumerable<T>> source, Func<IObservable<T>, ReactiveStatement> body)
        {
            Contract.Requires(source != null && body != null);
            _bodySelector = body;
            _block = new Block { Parent = this };
            _block.BindEnabled(this);
            _subscription = Extract(source).SubscribeWithClear(OnAdded, OnRemoved, OnCleared);
        }

        private void OnAdded(IObservable<T> item, int index)
        {
            if (index < 0)
                index = 0;
            else if (index >= Body.Count)
                index = Body.Count;

            Body.Insert(index, new ForEachItemStatement(item, CreateStatement));
        }
        private void OnRemoved(IObservable<T> item, int index)
        {
            if (index < 0 || index >= Body.Count || ((ForEachItemStatement)Body[index]).Item != item)
            {
                index = Body.IndexOf(st => ((ForEachItemStatement)st).Item == item);
                if (index == -1)
                    return;
            }

            var stmt = Body[index];
            Body.RemoveAt(index);
            stmt.Dispose();
        }
        private void OnCleared()
        {
            var stmts = Body.ToList();
            Body.Clear();
            foreach (var stmt in stmts)
                stmt.Dispose();
        }

        public override void Dispose()
        {
            base.Dispose();
            _block.Dispose();
            _subscription.Dispose();
        }
    }
}
